package com.ruoyi.util.flowable.expression;


import com.alibaba.fastjson.JSON;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.SneakyThrows;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.EvaluationState;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.javax.el.ELContext;
import org.flowable.common.engine.impl.javax.el.ValueExpression;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Expression implementation backed by a JUEL {@link ValueExpression}.
 *
 * @author Frederik Heremans
 * @author Joram Barrez
 */
public class JsExpression implements Expression {
    public static ScriptEngine engine;
    private static final long serialVersionUID = 1L;
    public String sequenceFlowId;
    protected String expressionText;
    protected ValueExpression valueExpression;
    protected ExpressionManager expressionManager;

    public TaskService getTaskService() {
        return SpringUtils.getBean(TaskService.class);
    }

    public RepositoryService getRepositoryService() {
        return SpringUtils.getBean(RepositoryService.class);
    }

    public HistoryService getHistoryService() {
        return SpringUtils.getBean(HistoryService.class);
    }

    public RuntimeService getRuntimeService() {
        return SpringUtils.getBean(RuntimeService.class);
    }

    public JsExpression(ExpressionManager expressionManager, ValueExpression valueExpression, String expressionText) {
        this.valueExpression = valueExpression;
        this.expressionText = expressionText;
        this.expressionManager = expressionManager;
        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        engine = scriptEngineManager.getEngineByName("javascript");
    }

    @SneakyThrows
    @Override
    public Object getValue(VariableContainer variableContainer) {
        ExecutionEntityImpl executionEntity = (ExecutionEntityImpl)variableContainer;
        FlowElement flowElement = executionEntity.getCurrentFlowElement();
        Boolean rs = false;
        List<SequenceFlow> outgoingFlows = new ArrayList<>();
        if(flowElement instanceof ExclusiveGateway) {
            ExclusiveGateway exclusiveGateway =  ((ExclusiveGateway) flowElement);
            outgoingFlows = exclusiveGateway.getOutgoingFlows();
        }else if(flowElement instanceof UserTask){
            UserTask userTask =  ((UserTask) flowElement);
            outgoingFlows = userTask.getOutgoingFlows();
        }else if(flowElement instanceof InclusiveGateway){
            InclusiveGateway inclusiveGateway =  ((InclusiveGateway) flowElement);
            outgoingFlows = inclusiveGateway.getOutgoingFlows();
        }
        Iterator<SequenceFlow> sequenceFlowIterator = outgoingFlows.iterator();
        SequenceFlow outgoingSequenceFlow = null;
        while (sequenceFlowIterator.hasNext()) {
            SequenceFlow sequenceFlow = sequenceFlowIterator.next();
            if(sequenceFlow.getConditionExpression()!=null && sequenceFlow.getConditionExpression().contains(expressionText)) {
                outgoingSequenceFlow = sequenceFlow;
                break;
            }
        }
        if(outgoingSequenceFlow == null){
            return false;
        }

        Map variables = executionEntity.getVariables();
        if(variables == null){
            return false;
        }

        expressionText = expressionText.replace("${c:js(startUserId,","");
        String js = expressionText.substring(1,expressionText.length()-2);
        js = "var p =  "+JSON.toJSONString(variables) +";" + js;
        System.out.println(outgoingSequenceFlow.getName());
        System.out.println(js);
        try {
            engine.eval(js);
        }catch (Exception e){
            throw new Exception(outgoingSequenceFlow.getName() + "表达式错误" + e.getMessage());
        }
        String appointor = (String)engine.get("appointor");
        rs = (Boolean) engine.get("rs");
        rs = rs && startUserIsIn(variables.get("startUserId").toString(),appointor,executionEntity.getProcessInstanceId(),outgoingSequenceFlow.getId());
        System.out.println(outgoingSequenceFlow.getName() + "-----" + rs);
        return rs;
    }

    public boolean startUserIsIn(String startUserId, String appointor,String procInsId,String taskKey){
        if(StringUtils.isEmpty(appointor) || appointor.equals("[]")){
            return true;
        }
        List<String> userList = new ArrayList<>(); //TaskCreatedListener.appointorUserTypeToUserListId("user",appointor,startUserId,procInsId,taskKey);
        return userList.contains(startUserId);
    }

    protected Object resolveGetValueExpression(ELContext elContext) {
        return valueExpression.getValue(elContext);
    }

    @Override
    public void setValue(Object value, VariableContainer variableContainer) {
        ELContext elContext = expressionManager.getElContext(variableContainer);
        Object originalVariableContainer = elContext.getContext(VariableContainer.class);
        elContext.putContext(VariableContainer.class, variableContainer);
        Object originalValueContext = elContext.getContext(EvaluationState.class);
        elContext.putContext(EvaluationState.class, EvaluationState.WRITE);

        try {
            resolveSetValueExpression(value, elContext);
        } catch (Exception e) {
            throw new FlowableException("Error while evaluating expression: " + expressionText, e);
        } finally {
            elContext.putContext(EvaluationState.class, originalValueContext);
            elContext.putContext(VariableContainer.class, originalVariableContainer);
        }
    }

    protected void resolveSetValueExpression(Object value, ELContext elContext) {
        valueExpression.setValue(elContext, value);
    }

    @Override
    public String toString() {
        if (valueExpression != null) {
            return valueExpression.getExpressionString();
        }
        return super.toString();
    }

    @Override
    public String getExpressionText() {
        return expressionText;
    }

}
